console.log("start js")

class countingDict extends Object {  //use object as a dictionary
	incrementItem(key, addValue = 1) {
		if (this.hasOwnProperty(key)) {
			this[key] += addValue
			if (this[key] == 0)
				delete this[key]
		}
		else
			this[key] = addValue
	}
	decrementItem(key) {
		if (this.hasOwnProperty(key)) {
			this[key]--
			if (this[key] == 0)
				delete this[key]
			else
				console.log("no this key")
		}
	}
}

class CalcString extends (String) {
	constructor(s = "") {
		super(s)
		this.str = s
		this.parInfo = []
	}
	parenthesisAnalysis() {
		let left = 0; let right = 0;
		for (let i = 0; i < this.str.length; i++) {
			this.parInfo[i] = { left: null, right: null }
			this.parInfo[i].left = left
			this.parInfo[i].right = right
			if (this.str[i] == "(")
				left++
			else if (this.str[i] == ")")
				right++
		}
	}
	findFreeNdx(op) {
		if (this.parInfo.length == 0)
			this.parenthesisAnalysis()
		op = op.replaceAll(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
		let re = new RegExp("[" + op + "]", "g")
		let matched = this.str.matchAll(re); let ndx = [];
		for (let i of matched)
			if (this.parInfo[i.index].left == this.parInfo[i.index].right)
				ndx.push(i.index)
		return ndx
	}
	splitIntoArrayBy(op) {
		let arr = []
		let ndx = this.findFreeNdx(op)
		if (ndx.length > 0) {
			if (ndx[0] == 0)
				arr.push(this.str[0])
			else
				ndx.unshift(-1)
			ndx.push(this.str.length)
			for (let i = 1; i < ndx.length; i++) {
				// arr.push(this.str.substring(ndx[i - 1] + 1, ndx[i]))
				arr.push(new CalcString(this.str.substring(ndx[i - 1] + 1, ndx[i])))
				arr.push(this.str[ndx[i]])
			}
		}
		arr = arr.filter(Boolean)
		return (arr.length > 0 ? arr : [this])
	}
	substitute(position = 0, by = "") {
		return new CalcString(this.str.substring(0, position) + by + this.str.substring(position + 1))
	}
	difference(other) {
		let s
		for (let i of this.str)
			s = other.str.replaceAll(i, "")
		for (let i of other.str)
			s += this.str.replaceAll(i, "")
		return s
	}

	mul(other) { return this.mulDiv(other, "*") }
	div(other) { return this.mulDiv(other, "/") }
	mulDiv(other, op) {
		if (!(isNaN(this.str) || isNaN(other.str)))
			return new CalcString(this.str * other.str)
		let result = this.zeroOneOperate(this.str, other.str, op)
		if (result)
			return result
		if (!(isNaN(this.str)))
			this.str = round(this.str, 2)
		if (!(isNaN(other.str)))
			other.str = round(other.str, 2)
		if (this.str[0] != "-")
			this.str = "+" + this.str
		if (other.str[0] != "-")
			other.str = "+" + other.str
		let ar1 = this.splitIntoArrayBy("/")
		let ar2 = other.splitIntoArrayBy("/")
		if (op == "*") {
			if (ar1.length > 1 && !(/[^\(\)\+\-]/.test(ar1[2].difference(ar2[0])))) {
				ar1.pop()
				ar1.pop()
				ar2[0] = new CalcString("1")
			}
			else if (ar2.length > 1 && !(/[^\(\)\+\-]/.test(ar2[2].difference(ar1[0])))) {
				ar2.pop()
				ar2.pop()
				ar1[0] = new CalcString("1")
			}
		}
		else if (op == "/") {
			if (!(/[^\(\)\+\-]/.test(ar1[0].difference(ar2[0])))) {
				ar1[0] = new CalcString("1")
				ar2[0] = new CalcString("1")
			}
		}
		this.str = ar1.join("")
		other.str = ar2.join("")
		result = this.zeroOneOperate(this.str, other.str, op)
		if (result)
			return result
		else
			return new CalcString(this.str + op + other.str)
	}

	// element add and sub:
	add(other) { return this.addSub(other, "+") }
	sub(other) { return this.addSub(other, "-") }
	addSub(other, op) {
		if (!(isNaN(this.str) || isNaN(other.str)))
			return (this.str + other.str)
		else {
			let result = this.zeroOneOperate(this.str, other.str, op)
			if (result)
				return result
			if (!(isNaN(this.str)))
				this.str = round(this.str, 2)
			if (!(isNaN(other.str)))
				other.str = round(other.str, 2)
			if (op == "+" && other.str[0] == "-")
				op = ""
			if (op == "-") {
				let reverse = { "+": "-", "-": "+", "*": "/", "/": "+" }
				if (this.str[0] != "-")
					this.str[0] = "+" + this.str
				let ar = this.splitIntoArrayBy("+-")
				for (let i = 0; i < ar.length; i += 2)
					ar[i] = reverse[ar[i]]
				op = ""
			}
			return new CalcString(this.str + op + other.str)
		}
	}

	cmp(other) {
		if (!(isNaN(this) || isNaN(other)))
			return this.sub(other)
		else
			return 0
	}

	outerParenthesisProcess() {
		if (this.str[0] == "(" && this.str[this.str.length - 1] == ")" && this.findFreeNdx("+-*/").length == 0)
			this.str = this.str.substring(1, this.str.length - 1)
	}
	zeroOneOperate(x, y, op) {
		if (op == "*") {
			if (x == "0" || y == "0")
				return new CalcString("0")
			if (x == "1")
				return new CalcString(y)
			if (x == "-1")
				return new CalcString("-" + y)
			if (y == "1")
				new CalcString(x)
			if (y == "-1")
				return new CalcString("-" + x)
		}
		else if (op == "/") {
			if (x == "0")
				return new CalcString("0")
			if (y == "0")
				return new CalcString(x + "/" + "0")
			if (y == "1")
				new CalcString(x)
			if (y == "-1")
				return new CalcString("-" + x)
		}
		else if (op == "+") {
			if (x == "0")
				return new CalcString(y)
			if (y == "0")
				new CalcString(x)
		}
		else if (op == "-") {
			if (x == "0")
				new CalcString("-" + y)
			if (y == "0")
				new CalcString(x)
		}
		return ""
	}
	round(number, precision) {
		return isNaN(number) ? number : Math.round(+number + "e" + precision) / Math.pow(10, precision)
	}
}//end class

class Matrix {
	constructor(m = 0, n = 0) {
		this.cRow = m
		this.cCol = n
		this.data = Array(m).fill(0).map(() => (Array(n).fill(0)))
		this.curTable = 0
		this.curTbody = 0
		this.CoIN = ""
	}
	// row multiply:
	_mul(row, factor) {
		for (let j = 0; j < this.cCol; j++)
			this.data[row][j] = this.data[row][j].mul(factor)
	}

	// row add:
	_addA2B(rowA, rowB) {
		for (let j = 0; j < this.cCol; j++)
			this.data[rowB][j] = this.data[rowA][j].add(this.data[rowB][j])
	}

	// element multiply and divide:
	// mul(x1, x2) {
	// 	if (!(x1 instanceof String || x2 instanceof String))
	// 		return (x1 * x2)
	// 	else if (x1 == 0 || x2 == 0)
	// 		return 0
	// 	else {
	// 		if (!(x1 instanceof String))
	// 			x1 = round(x1, 2)
	// 		if (!(x2 instanceof String)) {
	// 			x2 = round(x2, 2)
	// 			let tmp = x1; x1 = x2; x2 = tmp;
	// 		}
	// 		x1 = new CalcString(x1.toString())
	// 		x2 = new CalcString(x2.toString())
	// 		return strOp(x1, x2, "*")
	// 	}
	// }
	// div(x1, x2) {
	// 	if (!(x1 instanceof String || x2 instanceof String)) {
	// 		if (x1 == 0) { return 0 }
	// 		if (x2 == 0)
	// 			return new CalcString(x1 + "/" + "0")
	// 		else
	// 			return (x1 / x2)
	// 	}
	// 	else {
	// 		if (!(x1 instanceof String))
	// 			x1 = round(x1, 2)
	// 		if (!(x2 instanceof String))
	// 			x2 = round(x2, 2)
	// 		x1 = new CalcString(x1.toString())
	// 		x2 = new CalcString(x2.toString())
	// 		return strOp(x1, x2, "/")
	// 	}
	// }
	// element add and sub:
	// add(x1, x2) {
	// 	if (!(x1 instanceof String || x2 instanceof String))
	// 		return (x1 + x2)
	// 	else {
	// 		if (x1 == 0) { return x2 }
	// 		if (x2 == 0) { return x1 }
	// 		if (!(x1 instanceof String))
	// 			x1 = round(x1, 2)
	// 		if (!(x2 instanceof String))
	// 			x2 = round(x2, 2)
	// 		x1 = new CalcString(x1.toString())
	// 		x2 = new CalcString(x2.toString())
	// 		return strOp(x1, x2, "+")
	// 	}
	// }
	// sub(x1, x2) {
	// 	if (!(x1 instanceof String || x2 instanceof String))
	// 		return (x1 - x2)
	// 	else {
	// 		if (x1 == x2) { return 0 }
	// 		if (x1 == 0) {
	// 			if (x2[0] == "-")
	// 				return x2.substring(1)
	// 			else
	// 				return new CalcString("-" + x2)
	// 		}
	// 		if (x2 == 0) { return x1 }
	// 		if (!(x1 instanceof String))
	// 			x1 = round(x1, 2)
	// 		if (!(x2 instanceof String))
	// 			x2 = round(x2, 2)
	// 		x1 = new CalcString(x1.toString())
	// 		x2 = new CalcString(x2.toString())
	// 		return strOp(x1, x2, "-")
	// 	}
	// }
	// element compare:

	_swapLine(r1, r2) {
		for (let j = 0; j < this.cCol; j++) {
			let tmp = this.data[r1][j]
			this.data[r1][j] = this.data[r2][j]
			this.data[r2][j] = tmp
		}
	}
	_format() {
		for (let i = 0; i < this.cRow; i++)
			for (let j = 0; j < this.cCol; j++)
				if (!(isNaN(this.data[i][j])))
					this.data[i][j] = round(this.data[i][j], 3)
	}
	updateTable() {
		this.clearTable()
		this.fillTable()
	}
	clearTable() {
		this.curTbody.replaceChildren()
	}
	parseInput(input = "input1") {
		this.data = document.getElementById(input).value
		if (this.data.length == 0) { return }
		this.data = this.data.replaceAll(/[\n\t\s]+$/g, '')
		this.data = this.data.replaceAll(/\t+\n/g, '\n')
		this.data = this.data.replaceAll(/[\t ]+/g, '\t')
		this.data = this.data.split('\n')
		for (let i = 0; i < this.data.length; i++) {
			this.data[i] = this.data[i].split('\t')
			for (let j = 0; j < this.data[i].length; j++)
				this.data[i][j] = new CalcString(this.data[i][j])
			if (this.data[i].length > this.cCol)
				this.cCol = this.data[i].length
		}
		this.cRow = this.data.length
	}
	fillTable(tbId = "table1") {
		// let tbId = $("input[name='selTable']:checked").val()
		this.curTable = document.getElementById(tbId)
		// this.curTable.style.display = "block"
		this.curTbody = this.curTable.querySelector("tbody")
		this.curTbody.replaceChildren()
		for (let i = 0; i < this.cRow; i++) {
			let newRow = document.createElement("tr")
			this.curTbody.appendChild(newRow)
			for (let j = 0; j < this.cCol; j++) {
				let newCell = document.createElement("td")
				newCell.innerText = this.data[i][j].str
				newRow.appendChild(newCell)
			}
		}
		// this.curTable.style.display = "inline-block"
		this.curTable.style.visibility = "visible"
	}
	reducedEchlon() {
		let m = 0; let n = 0; let i; let j; let max; let factor = 1
		let mle //max leading element
		while (m < this.cRow && n < this.cCol) {
			mle = this.data[m][n]
			max = m
			i = m + 1
			while (i < this.cRow) {
				if (this.data[i][n].cmp(mle) > 0) {
					mle = this.data[i][n]
					max = i //max leading row
				}
				i++
			}
			if (max != m) { this._swapLine(max, m) }
			for (i = m + 1; i < this.cRow; i++) {
				factor = this.data[m][n].div(this.data[i][n])
				for (j = n; j < this.cCol; j++) {
					this.data[i][j] = this.data[i][j].mul(factor)
					this.data[i][j] = this.data[i][j].sub(this.data[m][j])
				}
			}
			m++
			n++
			while (m < this.cRow && n < this.cCol && isNaN(this.data[m][n]) == false && this.data[m][n] == 0)
				n++
		}
		for (i = 0; i < this.cRow; i++) {
			for (j = 0; j < this.cCol; j++) {
				if (this.data[i][j] != 0) break
			}
			factor = this.div(1, this.data[i][j])
			this._mul(i, factor)
			for (let k = i - 1; k >= 0; k--) {
				factor = this.div(this.data[k][j], this.data[i][j])
				for (let p = j; p < this.cCol; p++) {
					let tmp = this.data[i][p].mul(factor)
					this.data[k][p] = this.data[k][p].sub(tmp)
				}
			}
		}
		this._format()
	}
	augmentRightIN() {
		if (this.cCol != this.cRow) {
			alert('not a square mat')
			return
		}
		let n = this.cCol
		this.coIN = new MatIN(n, n)
		this.cCol *= 2
		for (let i = 0; i < this.cRow; i++)
			this.data[i] = this.data[i].concat(Array(n).fill(0))
		for (let i = 0; i < this.cRow; i++)
			this.data[i][i + n] = 1
	}
	cutRightIN() {
		this.cCol /= 2
		for (let i = 0; i < this.cRow; i++)
			this.data[i].length = this.cCol
		this.updateTable()
	}
	transpose() {
		let Mx = new Matrix(this.cCol, this.cRow)
		for (let i = 0; i < this.cRow; i++)
			for (let j = 0; j < this.cCol; j++)
				Mx.data[j][i] = this.data[i][j]
		return Mx
	}
	inverse() {
		let M2 = new Matrix(this.cRow, this.cCol)
		if (this.cRow != this.cCol) {
			alert('not a square mat')
			return
		}
		this.augmentRightIN()
		this.reducedEchlon()
		let n = this.cRow
		for (let i = 0; i < n; i++)
			for (let j = 0; j < n; j++)
				M2.data[i][j] = this.data[i][j + n]
		this.cutRightIN()
		this.parseInput()
		this.updateTable()
		return M2
	}
	matMultiply(otherM) {
		let M3 = new Matrix(this.cRow, otherM.cCol)
		if (this.cCol != otherM.cRow) {
			console.log('matrix unmatched')
			return
		}
		for (let i = 0; i < this.cRow; i++) {
			for (let j = 0; j < otherM.cCol; j++) {
				let x = 0
				for (let k = 0; k < this.cCol; k++) {
					x = this.data[i][k].mul(otherM.data[k][j]).add(x)
				}
				M3.data[i][j] = x
			}
		}
		M3._format()
		return M3
	}
} // class end

class MatIN extends Matrix {
	constructor(m, n) {
		if (m != n) { alert("not a square mat"); return; }
		super(m, n)
		for (let i = 0; i < m; i++)
			this.data[i][i] = 1
	}
}


let M1 = new Matrix(); let M2 = new Matrix();
function parseInput() {
	M1.parseInput("input1")
	M2.parseInput("input2")
	M1.fillTable("table1")
	M2.fillTable("table2")
}
function reducedEchlon() {
	M1.reducedEchlon()
	M1.fillTable()
}
function transposeM1() {
	M2 = M1.transpose()
	M2.fillTable("table2")
}
function inverseM1() {
	M2 = M1.inverse()
	M2.fillTable("table2")
}
function swapM1M2() {
	let M3 = M1
	M1 = M2; M2 = M3;
	M1.fillTable()
	M2.fillTable("table2")
}
function matMultiply() {
	let M3 = M1.matMultiply(M2)
	M3.fillTable("table3")
}
function chainMultiply(cMat = 3, n = 2) {
	let mats = Array(cMat)
	for (let c = 0; c < cMat; c++) {
		mats[c] = new Matrix(n, n)
	}
	for (let c = 0; c < cMat; c++) {
		for (let i = 0; i < n; i++) {
			for (let j = 0; j < n; j++) {
				mats[c].data[i][j] = new CalcString(String.fromCharCode(97 + c) + (i * n + j + 1).toString())
			}
		}
	}
	let m = mats[0].matMultiply(mats[1])
	for (let c = 2; c < cMat; c++) {
		m = m.matMultiply(mats[c])
	}
	m.fillTable("table3")
}

setInitValue()

function setInitValue() {
	document.getElementById("input1").value =
		// 		`3	4	1	3
		// 5	2	y	4
		// 1/2 x	0	5`
		// 		`3	4	a	c
		// 5	b	7	d
		// 1/2 6	0	e
		// `
		`a b 1 0
c d 0 1
e f 1 1/2`
}

BakstrOp(op, other) {
	let result = this._simpleCaseProcess(op, other)
	if (result) {
		result.needFrac = true
		return result
	}
	result = new CalcString()
	if (op == "+" || op == "-") {
		let s1; let s2;
		this.freeAddSubNdx = this.findFreeNdx("+-")
		other.freeAddSubNdx = other.findFreeNdx("+-")
		if (op == "-") {
			for (let i of other.freeAddSubNdx)
				other.substitute(i, dict1[other.str[i]])  // a - (-b-c) -> a - (+b+c)
			if (other.str[0] == "+") {   // a - (+b+c) -> a + b+c
				op = "+"
				other.str = other.str.substring(1)
				other._fraction()
				other.numerator.sort()
				other.str = other.numerator.join("*")
				if (other.denominator.length)
					other.str += "/" + other.denominator.join("*")
			}
			other.needFrac = true
		}
		s1 = new CalcString(this.str + op + other.str)
		//common denominator if possible:   d - a*b/c -> (d*c - a*b)/c 
		if (this.needFrac)
			this._fraction()
		if (other.needFrac)
			other._fraction()
		let [arr1, arr2] = [this.denominator, other.denominator]
		let commonDenominator = new Set(arr1.concat(arr2))
		commonDenominator = [...commonDenominator.keys()]
		if (commonDenominator.length) {
			for (let x of commonDenominator) {
				if (!this.denominator.includes(x)) {
					this.numerator.push(x)
					this.needReduce = true
				}
				if (!other.denominator.includes(x)) {
					other.numerator.push(x)
					other.needReduce = true
				}
			}
			if (this.needReduce)
				this.numerator = this.numerator.reduce((x, y) => x.strOp("*", y))
			else
				this.numerator = new CalcString(this.numerator.join("*"))
			if (other.needReduce)
				other.numerator = other.numerator.reduce((x, y) => x.strOp("*", y))
			else
				other.numerator = new CalcString(other.numerator.join("*"))
			this.numerator._delOuterParen()
			other.numerator._delOuterParen()
			s1 = this.numerator.strOp(op, other.numerator)  // upper part
			s2 = new CalcString(commonDenominator.sort().join("*")) //lower part
			if (commonDenominator.length > 1)
				s2.str = "(" + s2.str + ")"
		}
		//s1 can't have / now:
		for (let i of Object.entries(dict2))
			s1.str = s1.str.replaceAll(i[0], i[1])
		s1._addsubProcess()
		//s1 divide by s2:
		if (commonDenominator.length && s1.hasFree("+-"))
			s1.str = "(" + s1.str + ")"
		if (commonDenominator.length)
			result = s1.strOp("/", s2)
		else
			result = s1
	}
	else if (op == "/" || op == "*") {
		if (this.hasFree("+-"))
			this.str = "(" + this.str + ")"
		if (other.hasFree("+-"))
			other.str = "(" + other.str + ")"
		this._fraction()
		other._fraction()
		if (op == "*") {
			result.numerator = this.numerator.concat(other.numerator)
			result.denominator = this.denominator.concat(other.denominator)
		}
		else {
			result.numerator = this.numerator.concat(other.denominator)
			result.denominator = this.denominator.concat(other.numerator)
		}
		result._muldivProcess()
	}
	result.needFrac = true
	if (!isNaN(result.str))
		result = Number(result.str)
	return result
}
} //end of class

else if (op == "/" || op == "*") {
	if (this.hasFree("+-"))
		this._addOuterParen()
	if (other.hasFree("+-"))
		other._addOuterParen()
	this._fraction()
	other._fraction()
	if (op == "*") {
		result.numerator = this.numerator.concat(other.numerator)
		result.denominator = this.denominator.concat(other.denominator)
	}
	else {
		result.numerator = this.numerator.concat(other.denominator)
		result.denominator = this.denominator.concat(other.numerator)
	}
	result._muldiv()
}